package org.msfj.common.base;

import cn.hutool.core.collection.CollectionUtil;
import org.msfj.common.annotation.Constants;
import org.msfj.common.context.UserContextHandler;
import org.msfj.common.enums.Enable;
import org.msfj.common.exception.BusinessException;
import org.msfj.common.util.CacheUtil;
import org.msfj.common.util.IdWorker;
import org.msfj.common.util.InstanceUtil;
import cn.hutool.core.exceptions.ExceptionUtil;
import cn.hutool.core.util.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.CacheConfig;
import org.springframework.dao.DuplicateKeyException;
import org.springframework.transaction.annotation.Transactional;
import tk.mybatis.mapper.entity.Example;

import java.util.Date;
import java.util.List;

/**
 * @Description: 基础service
 * @User: program
 * @Date: 2017/4/25
 */
public abstract class BaseServiceImpl<T extends BaseModel> implements BaseService<T> {

    protected Logger logger = LoggerFactory.getLogger(this.getClass());

    @Autowired
    protected BaseMapper<T> mapper;

    @Override
    @Transactional
    public T saveOrEdit(T entity, Class<?>... cls) {
        try {
            String userId = UserContextHandler.getUserID();
            entity.setUpdateTime(new Date());
            if (StrUtil.isNotBlank(userId) && StrUtil.isBlank(entity.getUpdateBy())) {
                entity.setUpdateBy(userId);
            }
            if (StrUtil.isBlank(entity.getId())) {
                entity.setId(IdWorker.getId());
                entity.setCreateTime(new Date());
                if (StrUtil.isNotBlank(userId) && StrUtil.isBlank(entity.getCreateBy())) {
                    entity.setCreateBy(userId);
                }
                if (ObjectUtil.isNull(entity.getEnable())) {
                    entity.setEnable(Enable.ENABLE.getCode());
                }
                mapper.insertSelective(entity);
            } else {
                T org = getById(entity.getId());
                String lockKey = getLockKey(entity.getId());
                if (CacheUtil.getLock(lockKey)) {
                    try {
                        T update = InstanceUtil.getDiff(org, entity);
                        update.setId(entity.getId());
                        mapper.updateByPrimaryKeySelective(update);
                        entity = InstanceUtil.merge(org, entity);
                    } finally {
                        CacheUtil.unlock(lockKey);
                    }
                } else {
                    throw new BusinessException("数据不一致!请刷新页面重新编辑!");
                }
            }
            try {
                clearCache(cls);
            } catch (Exception e) {
                logger.error(Constants.Exception_Head, e);
            }
        } catch (DuplicateKeyException e) {
            logger.error(Constants.Exception_Head, e);
            throw new BusinessException("请勿重复添加.");
        } catch (Exception e) {
            logger.error(Constants.Exception_Head, e);
            throw new BusinessException(ExceptionUtil.getMessage(e));
        }
        return entity;
    }

    @Override
    @Transactional
    public void saveList(List<T> entityList, Class<?>... cls) {
        String userId = UserContextHandler.getUserID();
        try {
            if (CollectionUtil.isNotEmpty(entityList)) {
                for (T entity : entityList) {
                    entity.setId(IdWorker.getId());
                    entity.setCreateTime(new Date());
                    entity.setUpdateTime(new Date());
                    entity.setEnable(Enable.ENABLE.getCode());
                    entity.setCreateBy(userId);
                    entity.setUpdateBy(userId);
                }
                mapper.insertList(entityList);
                try {
                    clearCache(cls);
                } catch (Exception e) {
                    logger.error(Constants.Exception_Head, e);
                }
            }
        } catch (Exception e) {
            logger.error(Constants.Exception_Head, e);
            throw new BusinessException(ExceptionUtil.getMessage(e));
        }
    }

    @Override
    @Transactional
    public T delById(T entity, Class<?>... cls) {
        try {
            String userId = UserContextHandler.getUserID();
            entity.setUpdateTime(new Date());
            entity.setUpdateBy(userId);
            entity.setEnable(-1);
            mapper.updateByPrimaryKeySelective(entity);
            try {
                clearCache(cls);
            } catch (Exception e) {
                logger.error(Constants.Exception_Head, e);
            }
        } catch (Exception e) {
            logger.error(Constants.Exception_Head, e);
            throw new BusinessException(ExceptionUtil.getMessage(e));
        }
        return entity;
    }

    @Override
    @Transactional
    public void delByIds(List<String> keys, T entity, Class<?>... cls) {
        try {
            String userId = UserContextHandler.getUserID();
            Example example = new Example(entity.getClass());
            Example.Criteria criteria = example.createCriteria();
            criteria.andIn("id", keys);
            entity.setEnable(-1);
            entity.setUpdateTime(new Date());
            entity.setUpdateBy(userId);
            mapper.updateByExampleSelective(entity, example);
            try {
                clearCache(cls);
            } catch (Exception e) {
                logger.error(Constants.Exception_Head, e);
            }
        } catch (Exception e) {
            logger.error(Constants.Exception_Head, e);
            throw new BusinessException(ExceptionUtil.getMessage(e));
        }
    }

    @Override
    @Transactional
    public void trueDelById(Object id, Class<?>... cls) {
        try {
            mapper.deleteByPrimaryKey(id);
        } catch (Exception e) {
            logger.error(Constants.Exception_Head, e);
            throw new BusinessException(ExceptionUtil.getMessage(e));
        }
        try {
            clearCache(cls);
        } catch (Exception e) {
            logger.error(Constants.Exception_Head, e);
        }
    }

    @Override
    @Transactional
    public void trueDelByIds(List<String> keys, T entity, Class<?>... cls) {
        try {
            Example example = new Example(entity.getClass());
            example.createCriteria().andIn("id", keys);
            mapper.deleteByExample(example);
        } catch (Exception e) {
            logger.error(Constants.Exception_Head, e);
            throw new BusinessException(ExceptionUtil.getMessage(e));
        }
        try {
            clearCache(cls);
        } catch (Exception e) {
            logger.error(Constants.Exception_Head, e);
        }
    }

    @Override
    public T getById(Object id) {
        String key = getCacheKey(id);
        T record = null;
//        try {
//            record = (T) CacheUtil.getCache().get(key);
//        } catch (Exception e) {
//            logger.error(Constants.Exception_Head, e);
//        }
//        if (record == null) {
//            String lockKey = getLockKey(id);
//            if (CacheUtil.getLock(lockKey)) {
//                try {
//                    record = mapper.selectByPrimaryKey(id);
//                    try {
//                        CacheUtil.getCache().set(key, record);
//                    } catch (Exception e) {
//                        logger.error(Constants.Exception_Head, e);
//                    }
//                } finally {
//                    CacheUtil.unlock(lockKey);
//                }
//            } else {
//                logger.debug(getClass().getSimpleName() + ":" + id + " retry queryById.");
//                sleep(20);
//                return this.getById(id);
//            }
//        }
        record = mapper.selectByPrimaryKey(id);
        return record;
    }

    @Override
    public List<T> getByExample(Example example) {

        example.orderBy("createTime").asc();
        return mapper.selectByExample(example);
    }

    public void clearCache(Class<?>... cls) {
        CacheUtil.getCache().delAll(getCacheKey());
        if (ArrayUtil.isNotEmpty(cls)) {
            for (Class<?> cl : cls) {
                String cacheKey = getCacheKey(cl);
                CacheUtil.getCache().delAll(cacheKey);
            }
        }

    }

    protected void sleep(int millis) {
        try {
            Thread.sleep(RandomUtil.randomInt(10, millis));
        } catch (InterruptedException e) {
            logger.error("", e);
        }
    }

    /**
     * 获取缓存键值
     */
    protected String getCacheKey(Object id) {
        String cacheName = getCacheKey();
        return new StringBuilder().append(cacheName).append(":").append(id).toString();
//        return new StringBuilder("id:").append(id).toString();
    }

    /**
     * 获取缓存键值
     */
    protected String getLockKey(Object id) {
        String cacheName = getCacheKey();
        if (StrUtil.isBlank(cacheName)) {
            return null;
        }
        return new StringBuilder().append(cacheName).append(":LOCK:").append(id).toString();
    }

    /**
     * 获取service的@CacheConfig配置的缓存名称
     *
     * @return
     */
    private String getCacheKey() {
        Class<?> cls = getClass();
        String cacheName = Constants.cacheKeyMap.get(cls);
        if (StrUtil.isBlank(cacheName)) {
            CacheConfig cacheConfig = cls.getAnnotation(CacheConfig.class);
            if (cacheConfig == null) {
                return null;
            } else if (cacheConfig.cacheNames() == null || cacheConfig.cacheNames().length < 1) {
                cacheName = getClass().getName();
            } else {
                cacheName = cacheConfig.cacheNames()[0];
            }
            Constants.cacheKeyMap.put(cls, cacheName);
        }
        return cacheName;
    }

    /**
     * 此处约定service的cacheconfi注解中的名称必须为model的首字母小写
     *
     * @return
     */
    private String getCacheKey(Class<?> cls) {
        String cacheName = Constants.cacheKeyMap.get(cls);
        if (StrUtil.isBlank(cacheName)) {
            String className = cls.getName();
            int i = className.lastIndexOf(".");
            String lowerName = className.substring(i + 1, i + 2).toLowerCase() + className.substring(i + 2, className.length());
            cacheName = Constants.CACHE_NAMESPACE + lowerName;
            Constants.cacheKeyMap.put(cls, cacheName);
        }
        return cacheName;
    }
}
